package com.spring.core.io.support;

import com.spring.core.io.UrlResource;
import com.spring.logging.Log;
import com.spring.logging.LogFactory;
import com.spring.util.ConcurrentReferenceHashMap;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @author liyuncong
 * @version 1.0
 * @file SpringFactoriesLoader
 * @brief SpringFactoriesLoader
 * @details SpringFactoriesLoader
 * @date 2022-12-08
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2022-12-08               liyuncong          Created
 */
public final class SpringFactoriesLoader {

    public static final String FACTORIES_RESULT_LOCATION = "META-INF/spring.factories";

    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    static final Map<ClassLoader, Map<String, List<String>>> cache =
        new ConcurrentReferenceHashMap<>();

    private SpringFactoriesLoader() {
    }

    public static <T> List<T> loadFactories(Class<T> factoryType, ClassLoader classLoader) {
        // TODO Assert
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace(
                "Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
        }
        List<T> result = new ArrayList<>(factoryImplementationNames.size());
        for (String fa : factoryImplementationNames) {
            // TODO instantiateFactory
        }
        // TODO AnnotationAwareOrderComparator.sort(result);
        return result;
    }

    public static List<String> loadFactoryNames(Class<?> factoryType, ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getTypeName();
        return loadSpringFactories(classLoaderToUse)
            .getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            result = new HashMap<>();
            try {
                Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESULT_LOCATION);
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        String factoryTypeName = ((String) entry.getKey()).trim();
                        // TODO StringUtils
                    }
                }
            } catch (IOException exception) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESULT_LOCATION + "]", exception);
            }
        }
        return result;
    }
}
